package com.gmail.dmitryustimov.ms.java7.patternslib.visitor.reflective;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public abstract class Visitor {

    private String visitMethodName;
    private String visitableClassName;
    private String visitorClassName;

    public Visitor(String visitMethodName) {
        this.visitMethodName = visitMethodName;
        this.visitableClassName = Visitable.class.getCanonicalName();
        this.visitorClassName = Visitor.class.getCanonicalName();
    }

    public void visit(Visitable v) {
        Method m = null;
        try {
            m = findMethod(v);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            m.invoke(this, new Object[] { v });
        }
        catch(IllegalAccessException e1) {
            e1.printStackTrace();
        }
        catch(InvocationTargetException e2) {
            e2.printStackTrace();
        }
    }

    private Method findMethod(Visitable v) throws NoSuchMethodException {
        Class visitor = null;
        Class visitable = v.getClass();
        while ( isAncestorOf(visitableClassName, visitable)) {
            visitor = getClass();
            while ( isAncestorOf(visitorClassName, visitor)) {
                try {
                    Method m = visitor.getDeclaredMethod(visitMethodName,
                            new Class[]{visitable});
                    return m;
                } catch ( NoSuchMethodException e ) {
                    visitor = visitor.getSuperclass();
                }
            }
            visitable = visitable.getSuperclass();
        }
        throw new NoSuchMethodException("No such method : " + visitMethodName + " in " + visitor.getClass());
    }
    private boolean isAncestorOf(String ancestorName, Class descendant) {
        try {
            return Class.forName(ancestorName).isAssignableFrom(descendant);
        }
        catch ( ClassNotFoundException e ) {
            e.printStackTrace();
        }
        return false;
    }
}
